
# TODO: Look at using a scene graph to generalise layer system
"""
layer['play'] = SceneNode(parent=root_scene_node)
layer['gui'] = SceneNode(parent=root_scene_node)

class Something(object):
    def __init__(self):
        self.component1 = JoinTask

class SceneNode(object):
    def __init__(self, parent, xform):
        self.parents = [(parent, 1.0)]
        parent._attachChild(self)
        self.children = []
        self.xform = xform
        self.material = None
        self.mesh = None

    def attachTo(self, parent, weight=1.0):
        if weight > 1.0:
            weight = 1.0
        # Check for complete re-parenting
        if weight >= 1.0-0.0000001:
            for parent in self.parents[:]:
                parent[0]._detachChild(self)
                parents.remove(parent)
        # Else adjust the weights of existing parents
        else:
            for parent in self.parents:
                parent[1] *= 1-weight
        # And attach to the new parent
        self.parents.append((parent, weight))
        parent._attachChild(self)

    def _attachChild(self, child):
        self.children.append(child)
    def _detachChild(self, child):
        self.children.remove(child)

    def getGlobalXForm(self):
        xform = XForm.getIdentity()
        # Weighted sum of all parent xforms
        for (parent, weight) in self.parents:
            xform += parent.getXForm() * weight
        # Plus our local xform
        xform += self.xform
        return xform

    def _render(self, renderer):
        renderer.pushMatrix()
        renderer.applyXForm(self.xform)
        
        if self.material != None and self.mesh != None:
            renderer.bindMaterial(self.material)
            renderer.renderMesh(self.mesh)

        for child in self.children:
            child._render(renderer)
        renderer.popMatrix()

class Renderer:
    def renderSceneNode(self, sn):
        glPushMatrix()
        self.applyXForm(sn.xform)
        if sn.material != None:
            self.bindMaterial(sn.material)
        if sn.mesh != None:
            self.renderMesh(sn.mesh)
        for child in sn.children:
            self.renderSceneNode(child)
        glPopMatrix()

#class Vec2(object):
#    def __init__(self, x, y):
#        self.vals = [x, y]
#        # TODO: Begin with code on PyGame site

#class Mat22(object):
#    def __init__(self, ...

# TODO: Use these for now, but eventually
#       use a new implementations.
#----------------------------------------
class Vec2(b2Vec2):
    pass
class Mat22(b2Mat22):
    pass
class XForm(b2XForm):
    def __init__(self):
        self.pos = Vec2()
        self.rot = Mat22()
        self.scale = Vec2()
        # ...
        pass
"""
"""
class SceneNode(object):
    def __init__(self, parent):
        self.parent = parent
        self.parent.attachChild(self)
        self.children = []
        self.xform = XForm()

    def attachChild(self, child):
        self.children.append(child)

    def detachChild(self, child):
        self.children.remove(child)

    def reparentTo(self, new_parent):
        self.parent.detachChild(self)
        self.parent = new_parent
        new_parent.attachChild(self)
"""
"""
class ParallaxScrollingController(Task):
    def __init__(self, camera):
        self.camera = camera
        self.use_parallax = (True, False)
        self.layers = []
    def enableParallax(self, horiz, vert):
        self.use_parallax = (horiz, vert)
    def controlLayer(self, scene_node, parallax):
        self.layers.append((scene_node, parallax))
    def onTick(self):
        pass
    def onFrame(self):
        for layer in self.layers:
            layer.scene_node.setXForm( ... )
"""
from Engine import *

#import SafeEval

the_engine = Engine()

def IndexGenerator():
    i=0
    while True:
        yield i
        i += 1
getNextCommandIndex = IndexGenerator().next
#getNextSomeOtherIndex = IndexGenerator().next

# The first game state added is the initial game state used by the engine
#engine.addGameState("splash_screen")
#engine.addGameState("play")
#engine.addGameState("menu")
# create splash models
# create timer
# when timer ticks, hide current splash model and show next one
# when reached end of splash model list, change state to menu
#engine.changeGameStateTo("menu", keep_current_in_memory=False)
# create background model
# create menu gui
# respond to gui events
# when "Play" clicked, change state to play
#engine.changeGameStateTo("play", keep_current_in_memory=True, \
#                         keep_current_running=False)
# check for resumed pause
	# resume
# else
	# load level
# run game
# if pause, change state to menu
#engine.changeGameStateTo("menu", keep_current_in_memory=True)
# when game ends, change state to menu
#engine.changeGameStateTo("menu", keep_current_in_memory=False)

# state = game
# pause
# change state to menu, keeping game in memory
# new game clicked
# 

class PrototypeGame(object):

    def __init__(self):
        # Engine settings
        the_engine.tickRate = 60
        the_engine.gravity = -9.8
        the_engine.worldMinX = -20.0
        the_engine.worldMinY = -20.0
        the_engine.worldMaxX = 20.0
        the_engine.worldMaxY = 20.0
        # TODO: Work out why turning this on makes the physics go haywire
        the_engine.usePhysicsSleeping = False
        the_engine.windowWidth = 800
        the_engine.windowHeight = 600
        the_engine.forceResolution = True
        the_engine.fullscreen = False
        #the_engine.physicsIterations = 10
        the_engine.velocity_iterations = 10
        the_engine.position_iterations = 8
        the_engine.init()

        the_engine.appendEventHandler('key', self.handleKeyEvent)
        the_engine.appendEventHandler('network_packet_received', \
                                   self.handleNetworkPacketReceivedEvent)
        
        the_engine.renderer.enableParallax(True, False)

        # Set up the render layers
        the_engine.renderer.addLayer("background", 0)
        the_engine.renderer.addLayer("distant", 0.5)
        the_engine.renderer.addLayer("behind", 1)
        the_engine.renderer.addLayer("play", 1)
        the_engine.renderer.addLayer("infront", 1)
        the_engine.renderer.addLayer("foreground", 2.0)
        the_engine.renderer.addLayer("GUI", 0)

        # Set up the camera
        the_engine.renderer.camera.setMaxZoomRate(0.01)
        the_engine.renderer.camera.setZoomAccel(0.01)
        the_engine.renderer.camera.zoomDirectTo(0.1)
        the_engine.renderer.camera.zoomTo(0.2)

        # TODO: Remove this and replace with the level's solid_geometry
        groundShapeDef = b2PolygonDef()
        groundShapeDef.SetAsBox(40.0, 0.1, b2Vec2(0.0, -3.0), 0.0)
        the_engine.physics_world.GetGroundBody().CreateShape(groundShapeDef)

        world = the_engine.world
        
        self.bunny1 = world.createEntity(Bunny, \
                                             "Bunny1", \
                                             b2Vec2(0.0, 0.0))

        self.key_bindings = {}
        self.loadKeyBindings()

        #the_engine.renderer.camera.followBody(self.playerChar.referenceBody)
        
        #for i in range(20):
        #    world.createEntity(Body, \
        #                       None, \
        #                       b2Vec2(-0.5, 0.2+0.1*i))

        level = world.createEntity(Level, "level")
        level.load("BoundnPound_1")

        the_engine.run()
        
    def loadKeyBindings(self):
        self.key_bindings['quit'] = pygame.K_ESCAPE
        self.key_bindings['pause'] = pygame.K_p
        
        self.key_bindings['up'] = pygame.K_UP
        self.key_bindings['down'] = pygame.K_DOWN
        self.key_bindings['left'] = pygame.K_LEFT
        self.key_bindings['right'] = pygame.K_RIGHT
        
    #@the_engine.event_handler(event="key")
    def handleKeyEvent(self, action, key, modifiers):
        #action = kwargs['action']
        #key = kwargs['key']
        #modifiers = kwargs['modifiers']
        # Handle player commands
        for command in self.bunny1.commands:
            if key == self.key_bindings[command]:
                self.bunny1.giveCommand(command, value=action=='down')
                return
        # Handle other game commands
        if key == self.key_bindings['quit']:
            self.quit()
        elif key == self.key_bindings['pause']:
            self.pause()
        # ...
        return True

    #@the_engine.event_handler(event="network_packet_received")
    def handleNetworkPacketReceivedEvent(self, type_id, system_address):
        if type_id == PyRakNet.ID_CONNECTION_REQUEST_ACCEPTED:
            pass
        return True

    def quit(self):
        the_engine.running = False

class Bunny(CompositeBody):

    # Quick up\down\left\right direction vectors conveniently
    # matching the command names.
    directions = {'up': b2Vec2(0.0, 1.0), \
                  'down': b2Vec2(0.0, -1.0), \
                  'left': b2Vec2(-1.0, 0.0), \
                  'right': b2Vec2(1.0, 0.0)}
    
    def __init__(self, engine, name, pos):
        CompositeBody.__init__(self, engine, name)
        self.size = 1.0
        body = engine.bodyLoader.createCircleBody(pos, \
                                                  radius=self.size/2.0, \
                                                  density=1.0, \
                                                  restitution=0.0, \
                                                  fixed_rotation=True)
        self.addBody('body', \
                     body, \
                     make_reference_body = True)
        self.models['body'] = engine.renderer.createModel(layer="play", \
                                    mesh="box", \
                                    texture="bunny.png", \
                                    influences=[(self.getXForm(), 1.0)], \
                                    local_xform=b2XForm_identity, \
                                    color=(1.0, 0.3, 0.5, 1.0), \
                                    scale=(self.size, self.size))
        self.walk_force = 5.0
        self.max_walk_speed = 2.5
        self.brake_force = 4.0
        self.max_jump_time = 0.05
        self.max_jump_force = 40.0
        
        self.commands = ["up", "down", "left", "right"]
        self.hasCommand = dict([(command, False) for command in self.commands])
        #self.hadCommand = dict([(command, False) for command in self.commands])
        self.direction = b2Vec2_zero
        self.jumpStartTime = time.clock()
        self.is_on_ground = False
        
    ##@NetworkedEvent
    #@netevent
    def giveCommand(self, command, value):
        """Contains state context logic for response to input commands.

            Control keys must be bound to this method and no other PlayerChar
             methods to ensure the event is sent to the server and to prevent
             1337 h4x0ring.
        """
        
        # TODO: See if can avoid the need for this when implementing jumping and crouching
        #self.hadCommand[command] = self.hasCommand[command]
        self.hasCommand[command] = value

        # If command is starting
        if value == True:
            if command == 'left':
                self.direction.x = -1.0
            elif command == 'right':
                self.direction.x = 1.0
            elif command == 'up':
                if self.is_on_ground:
                    self.direction.y = 1.0
                    self.jumpStartTime = time.clock()
                else:
                    # Prevent jumping unless on the ground
                    self.hasCommand['up'] = False
            elif command == 'down':
                pass
        # Else if command is stopping
        else:
            if not self.hasCommand['left'] and not self.hasCommand['right']:
                self.direction.x = 0.0
            if not self.hasCommand['up']:
                self.direction.y = 0.0

    def update(self, timeStep):

        # TODO: Do this properly, using contact normals
        self.is_on_ground = (self.getPos().y <= -2.405)
        
        """
        if self.isJumping:
            jumpTime = self.engine.timeMgr.getTime() - self.jumpStartTime
            if jumpTime >= PlayerChar.MAX_JUMP_TIME:
                self.__jump('stop')
        """
        """
                self.isJumping = False
                self.propulsionForce = b2Vec2_zero
                self.setMaxPropulsionSpeed(0.0)
            elif jumpTime < PlayerChar.JUMP_DELAY:
                self.jumpForce += PlayerChar.JUMP_FORCE
            elif jumpTime >= PlayerChar.JUMP_DELAY:
                self.propulsionForce = b2Vec2(0.0, self.jumpForce)
                self.jumpForce = 0.0
        """
        #self.is_on_ground = True#(self.bodies["LeftFoot"].GetWorldCenter().y <= -1.0 + 0.3)
        
        # TODO: Consider finding velocity dot surface vector instead of this
        vel = self.getLinearVelocity().x
        speed = abs(vel)

        # Check for no desired motion
        if self.direction.x == 0.0:
            if self.is_on_ground:
                # Apply damping to simulate braking with feet
                self.applyForce(self.brake_force * b2Vec2(-1.0*speed*vel, 0.0))
            else:
                # No free-motion air damping
                pass
        # Check for desired direction changes
        elif self.direction.x * vel < 0.0:
            if self.is_on_ground:
                # Apply damping to simulate changing direction with feet
                self.applyForce(self.brake_force * b2Vec2(-1.0*speed*vel, 0.0))
            else:
                # Allow aerial direction changes too, because real physics isn't fun
                self.applyForce(self.brake_force * b2Vec2(-1.0*speed*vel, 0.0))
        
        # Check for ground movement commands
        if self.hasCommand['left'] or self.hasCommand['right']:
            
            #else: # movement is already in the desired direction
            if self.is_on_ground:
                # Apply ground movement
                if speed < self.max_walk_speed:
                    self.applyForce(self.walk_force * self.direction)
            else:
                # Apply air movement
                if speed < self.max_walk_speed:
                    self.applyForce(self.walk_force * self.direction)
        """
        else: # if not moving horizontally
            if self.is_on_ground:
                # Apply damping to simulate braking with feet
                self.applyForce(self.brake_force * b2Vec2(-1.0*speed*vel, 0.0))
            else:
                # No damping in the air
                pass
        """

        # Apply jumping
        if self.hasCommand['up']:
            #jump_time = time.clock() - self.jumpStartTime
            #if jump_time < self.max_jump_time:
            if self.getPos().y - -2.405 < 0.2:
                self.applyForce(b2Vec2(0.0, self.max_jump_force))
            else:
                self.hasCommand['up'] = False
            
        #CharacterBody.update(self, timeStep)

        # Force angle to remain fixed at 0
        # Note: When this was being used, it caused the body to maintain a small velocity at all times for some reason
        #self.setXForm(b2XForm(self.getPos(), b2Mat22(0)))
        
#setTrace()

game = PrototypeGame()
